package mediator;

import java.io.IOException;
import java.util.HashMap;

import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import entity.EEntity;
import entity.EEntity.Datatypes;
import entity.EPattern;
import foundation.FConfigFile;
import foundation.FFileCommunication;

public class MFileCommunication {
	private FFileCommunication fFileCom;
	private FConfigFile fConfigFile, fGraphConfigFile;
	private String settingsFileName = "settings.conf";
	private String errorLogFileName = "errorLog.txt";
	private String errorLogPath;
	private String ODRoot = "OD/";
	private String dataTypeFile = "DataTypes.xml";
	private String ODFile = "OD.xml";
	private HashMap<Integer,String> dataTypes = new HashMap<Integer,String>();
	private HashMap<String,HashMap<Integer,String>> allDataTypes = new HashMap<String,HashMap<Integer,String>>();
	private EEntity eEntity;
	
	public MFileCommunication(EEntity eEntity, String rootPath) {
		this.ODRoot = rootPath + ODRoot;
		this.dataTypeFile = ODRoot + dataTypeFile;
		this.ODFile = ODRoot + ODFile;
		this.eEntity = eEntity;
		this.errorLogPath = rootPath + errorLogFileName;
		this.fConfigFile = new FConfigFile(rootPath + settingsFileName);
		
		String chosenGraphConfigFile = fConfigFile.getParameter("graphconfigfile");
		if (chosenGraphConfigFile != null) {
			this.fGraphConfigFile = new FConfigFile(chosenGraphConfigFile);
		}
		
		this.fFileCom = new FFileCommunication();
		fFileCom.createFile(errorLogPath);
		materializeNetwork();
	}
	
	public void materializeNetwork() {
		try {
		Document doc = fFileCom.readXmlFile(dataTypeFile);
		NodeList nodeList = doc.getElementsByTagName("DataType");
		
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node curNode = nodeList.item(i);
			
			if (curNode.getNodeType() == Node.ELEMENT_NODE) {
				Element nodeElement = (Element) curNode;
				String formatName = nodeElement.getElementsByTagName("Name").item(0).getChildNodes().item(0).getNodeValue();
				try {
					int formatId = Integer.parseInt(nodeElement.getAttribute("id"));
					dataTypes.put(formatId, formatName);
				} catch (NumberFormatException e) {
					addError("Error parsing " + dataTypeFile + ". id is not a number.");
				}
			}
		}
		
		doc = fFileCom.readXmlFile(ODFile);
		nodeList = doc.getElementsByTagName("node");
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node curNode = nodeList.item(i);
			Element nodeElement = (Element) curNode;
			NodeList nodeRegisters = nodeElement.getElementsByTagName("TPDO");
			String nodeName = nodeElement.getElementsByTagName("name").item(0).getChildNodes().item(0).getNodeValue();
			int nodeID = 0, objectIndex = 0, BRAMOffset = 0;
			
			try {
				nodeID = Integer.parseInt(nodeElement.getElementsByTagName("nodeid").item(0).getChildNodes().item(0).getNodeValue());
				objectIndex = Integer.parseInt(nodeElement.getElementsByTagName("ObjectIndex").item(0).getChildNodes().item(0).getNodeValue());
				BRAMOffset = Integer.parseInt(nodeElement.getElementsByTagName("BRAMOffset").item(0).getChildNodes().item(0).getNodeValue());
			} catch (NumberFormatException nfe) {
				addError("Error parsing " + ODFile + " at " + nodeName + ". nodeid, ObjectIndex or BRAMOffset is not a number.");
			}
			
			eEntity.addNode(nodeName, nodeID, objectIndex, BRAMOffset);
			
			for (int j = 0; j < nodeRegisters.getLength(); j++) {
				Node curRegister = nodeRegisters.item(j);
				Element registerElement = (Element) curRegister;

				int registerAddress = Integer.parseInt(registerElement.getAttribute("nodeindex"));
				String registerName = registerElement.getElementsByTagName("PDOName").item(0).getChildNodes().item(0).getNodeValue();
				String dataTypePath = ((Element) registerElement.getElementsByTagName("PDODataType").item(0)).getAttribute("path");
				int dataTypeId = Integer.parseInt(((Element) registerElement.getElementsByTagName("PDODataType").item(0)).getAttribute("id"));
				String dataType = getRegisterDatatype(dataTypePath,dataTypeId);

				String registerDescription = registerElement.getElementsByTagName("PDODesc").item(0).getChildNodes().item(0).getNodeValue();

				String registerUnit = "";
				try {
					registerUnit = registerElement.getElementsByTagName("PDOUnit").item(0).getChildNodes().item(0).getNodeValue();
				} catch (NullPointerException npe) {}

				double min = 0;
				double max = 0;
				try {
					NodeList dataRanges = registerElement.getElementsByTagName("PDOValueRange").item(0).getChildNodes();
					min = Double.parseDouble(((Element) dataRanges.item(0)).getChildNodes().item(0).getNodeValue());
					max = Double.parseDouble(((Element) dataRanges.item(1)).getChildNodes().item(0).getNodeValue());
				}  catch (Exception e) {}

				if (!Datatypes.contains(dataType)) {
					EPattern[] patterns = getPattern(dataTypePath,dataTypeId);

					if (patterns != null) {
						eEntity.addRegister(nodeName, registerName, dataType, (BRAMOffset + registerAddress)*4, registerDescription, registerUnit,patterns);
					} else {
						eEntity.addRegister(nodeName, registerName, dataType, (BRAMOffset + registerAddress)*4, registerDescription, registerUnit, min, max);
					}
				} else {
					eEntity.addRegister(nodeName, registerName, dataType, (BRAMOffset + registerAddress)*4, registerDescription, registerUnit, min, max);
				}
			}
		}
		
		// Clear datatype list to save memory:
		allDataTypes.clear();
		} catch (ParserConfigurationException pce) {
			addError("Parser Configuration Error when trying to parse OD-files: " + pce.getMessage());
			System.exit(1);
		} catch (SAXException saxe) {
			addError("Unable to parse OD-files: "+ saxe.getMessage());
			System.exit(1);
		} catch (IOException ioe) {
			addError("Unable to access OD-files: " + ioe.getMessage());
			System.exit(1);
		}
	}
	
	private String getRegisterDatatype(String path, int id) throws 
						ParserConfigurationException,SAXException,IOException {
		
		if (allDataTypes.get(path) == null) {
			Document doc = fFileCom.readXmlFile(ODRoot + path);
			NodeList nodeList = doc.getElementsByTagName("DataType");
			HashMap<Integer,String> dataTypes = new HashMap<Integer,String>();
			
			for (int i = 0; i < nodeList.getLength(); i++) {
				Node curNode = nodeList.item(i);
				
				if (curNode.getNodeType() == Node.ELEMENT_NODE) {
					Element nodeElement = (Element) curNode;
					
					dataTypes.put(Integer.parseInt(nodeElement.getAttribute("id")), nodeElement.getElementsByTagName("Name").item(0).getChildNodes().item(0).getNodeValue());
				}
			}
			allDataTypes.put(path, dataTypes);
		}
		
		return allDataTypes.get(path).get(id);
	}
	
	private EPattern[] getPattern(String path, int id) 
				throws ParserConfigurationException,SAXException,IOException{
		Document doc = fFileCom.readXmlFile(ODRoot + path);
		NodeList nodeList = doc.getElementsByTagName("DataType");
		
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node curNode = nodeList.item(i);
			
			if (curNode.getNodeType() == Node.ELEMENT_NODE) {
				Element nodeElement = (Element) curNode;
				
				if (Integer.parseInt(nodeElement.getAttribute("id")) == id) {
					NodeList patternDefList = nodeElement.getElementsByTagName("BitPatternDef");
					
					if (patternDefList.getLength() > 0) {
						NodeList patternList = ((Element) patternDefList.item(0)).getElementsByTagName("Pattern");
						EPattern[] patterns = new EPattern[patternList.getLength()];
						
						for (int j = 0; j < patternList.getLength(); j++) {
							Element patternElement = (Element) patternList.item(j);
							
							int base = Integer.parseInt(patternElement.getAttribute("base"));
							String pattern = patternElement.getAttribute("value");
							String text = patternElement.getChildNodes().item(0).getNodeValue();
							
							patterns[j] = new EPattern(pattern,base,text);
						}
						
						return patterns;
					} else {
						return null;
					}
				}
			}
		}
		
		return null;
	}
	
	public void addError(String line) {
		try {
			System.err.println(line);
			fFileCom.addLineWithTime(errorLogPath, line);
		} catch (Exception e) {
			System.err.println("Unable to access filesystem");
		}
	}
	
	public String getSettingsParameter(String parameter) {
		return fConfigFile.getParameter(parameter);
	}
	
	public boolean setSettingsParameter(String parameter, String value) {
		try {
			fConfigFile.setParameter(parameter,value);
			return true;
		} catch (Exception e) {
			addError("Unable to save parameter: " + parameter + ". Message: " 
					+ e.getMessage());
			return false;
		}
	}
	
	public boolean setGraphSettingsFile(String fileName) {
		try {
			fGraphConfigFile = new FConfigFile(fileName);
			fConfigFile.setParameter("graphconfigfile",fileName);
			return true;
		} catch (Exception e) {
			addError("Unable to save parameter: graphconfigfile. Message: " 
					+ e.getMessage());
			return false;
		}
	}
	
	public String getGraphSettingsParameter(String parameter) {
		if (fGraphConfigFile != null) {
			return fGraphConfigFile.getParameter(parameter);
		} else {
			return null;
		}
	}
	
	public boolean setGraphSettingsParameter(String parameter, String value) {
		try {
			fGraphConfigFile.setParameter(parameter,value);
			return true;
		} catch (Exception e) {
			addError("Unable to save parameter: " + parameter + ". Message: " 
					+ e.getMessage());
			return false;
		}
	}
}
